useEffect の無限ループをいつの間にかしなくなった
#React でもう長いこと useEffect の無限ループを起こしていない。 でも「どうやって useEffect の無限ループを避けているの?」と聞かれると意外と上手く答えられないことに気がついた。
完全に暗黙知と化しているらしい。
eslint の exhaustive-deps を使っているから?
部分的には正しいが、exhaustive-deps は必ずしも無限ループを防げるわけではない
code:typescript
// eslint に怒られないが、無限ループする例
useEffect(() => {
setCounter(prev => prev + 1)
そもそも useEffect 内で setState をあまりしない?
確かにあまりしない
が、別に useEffect 内のステート更新を全部禁止する必要はないと思うし、現に今でもときどき書く
すると、やっていいケースとダメなケースを無意識に見分けていることになる
たとえば以下は許容されうる
code:typescript
const useItemApi = (id: number) => {
// id が変わったら refetch される
// 無駄な再レンダリングはあるかもしれないが、無限ループは( id が無限に更新されない限り )起きない
useEffect(() => {
fetch(https://api.example.com/items/${id})
.then(r => r.json())
.then(setItem)
return item
}
swr や react-query を入れてない環境でこういうコードが出てきてもまぁそこまでおかしくはない
useEffect の中で setState をする場合、基本的に非同期処理の終わった後( .then の中 )か、特定の条件下( if 文の中 )のことが多い
useEffect の直下で、無条件かつ同期的に setState を呼ぶというシチュエーションがそもそも珍しい気がする
見たらリファクタリングを考えると思う
useEffect の依存配列に state を入れてないから?
これは結構ありそう
props に依存して state を更新はありうるけど、state に依存して別の state を更新するのは不吉な匂いを感じる
「許容できるピタゴラスイッチ度」みたいな概念があったとして、前者は閾値を超えないが後者は超えると感じる
ピタゴラスイッチ度(?)が高いことはコードスメルが悪いことの指標かもしれない
たとえば useItemApi の例では(おそらく props として受け取るだろう)id を唯一の依存にしている
もちろん、props がグローバルステート由来で……みたいなケースも考えられはするが、id が他の state に依存して頻繁に変わるなんてことあるだろうか?
一方 counter の例は、useState の結果が依存配列にいる
こういうのは不吉に感じる
これを無意識に避けるようになった?
というわけで、自分は依存配列に入れると不吉そうな値を無意識に見分けていることが分かった。